package com.yugao.fintech.antelope.gateway.auth;

import com.yugao.fintech.antelope.base.model.constants.OAuth2Cons;
import com.yugao.fintech.antelope.base.model.constants.RequestCons;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.stereotype.Component;
import org.springframework.util.MimeType;
import org.springframework.web.server.ServerWebExchange;

import java.util.List;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 校验 token, 请求需要授权的接口时候会走当前过滤器
 */
@Component
public class MyBearerTokenResolver {
    private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?<token>[a-zA-Z0-9-:._~+/]+=*)$",
            Pattern.CASE_INSENSITIVE);

    private static final boolean allowFormEncodedBodyParameter = false;

    private static final boolean allowUriQueryParameter = false;

    private static final String httpTokenHeaderKey = HttpHeaders.AUTHORIZATION;


    public String resolve(ServerWebExchange exchange) {
        ServerHttpRequest request = exchange.getRequest();
        final String authorizationHeaderToken = resolveFromAuthorizationHeader(request);
        final String parameterToken = isParameterTokenSupportedForRequest(request)
                ? resolveFromRequestParameters(request) : null;
        if (authorizationHeaderToken != null) {
            if (parameterToken != null) {
                throw new OAuth2AuthenticationException("Found multiple bearer tokens in the request");
            }
            return authorizationHeaderToken;
        }
        if (parameterToken != null && isParameterTokenEnabledForRequest(request)) {
            return parameterToken;
        }
        return null;
    }

    private String resolveFromAuthorizationHeader(ServerHttpRequest request) {
        HttpHeaders headers = request.getHeaders();
        String authorization = headers.getFirst(httpTokenHeaderKey);
        authorization = StringUtils.isEmpty(authorization) ?
                OAuth2Cons.TokenType.BEARER + " " + headers.getFirst(RequestCons.Headers.WEB_SOCKET_TOKEN)
                : authorization;

        if (!StringUtils.startsWithIgnoreCase(authorization, OAuth2Cons.TokenType.BEARER)) {
            return null;
        }
        Matcher matcher = authorizationPattern.matcher(authorization);
        if (!matcher.matches()) {
            throw new OAuth2AuthenticationException("Bearer token is malformed");
        }
        return matcher.group("token");
    }

    private static String resolveFromRequestParameters(ServerHttpRequest request) {
        List<String> values = request.getQueryParams().get("access_token");
        if (values == null || values.size() == 0) {
            return null;
        }
        if (values.size() == 1) {
            return values.get(0);
        }
        return null;
    }

    private boolean isParameterTokenSupportedForRequest(final ServerHttpRequest request) {
        String method = Optional.ofNullable(request.getMethod()).map(Enum::name).orElse("");
        String contentType = Optional.ofNullable(request.getHeaders().getContentType()).map(MimeType::getType).orElse("");
        return (("POST".equals(method)
                && MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(contentType))
                || "GET".equals(method));
    }

    private boolean isParameterTokenEnabledForRequest(final ServerHttpRequest request) {
        String method = Optional.ofNullable(request.getMethod()).map(Enum::name).orElse("");
        String contentType = Optional.ofNullable(request.getHeaders().getContentType()).map(MimeType::getType).orElse("");
        return ((allowFormEncodedBodyParameter && "POST".equals(method)
                && MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(contentType))
                || (allowUriQueryParameter && "GET".equals(method)));
    }
}
